Introduction

Video games are about learning to make good decisions. I always ejoyed the liberty they offer through the choices available.

Mario Kart was one of my first game I played as a kid, then on SNES and now on Switch. Back then the was was simple between big and heavy donkey kong or the sneaky Toad, now we get to chose the character,but also kart parts : kart frame, tires and glider (yes, mario kart is also about glidding now).

So, Is there any best combination ?

Libraries

This project recquired to use the libraries listed below :

library(tidyverse)
library(readxl)
library(knitr)
library(kableExtra)
library(skimr)
library(GGally)
library(gtools) # Generate combination
library(patchwork)
library(magick)
library(png)
library(ggridges)
library(plotly)

Data

Source

A quick reddit query I found a dataset (https://docs.google.com/spreadsheets/d/1g7A-38tn9UAIbB2B3sZI-MpILsS3ZS870UTVMRRxh4Q/edit#gid=0) based on in-game experiments filled by Luigi_Fan2’s (https://twitter.com/Luigi_Fan2).

In its annotations, we can see that there are many redundancies in the karts and parts. Please, refer to this table if you want equivalent stats but different skins.

Import Data

Instead of directly importing data from google drive, I built an excel file to ease up the wrangling process. The modified excel file is downloadable here. I organized the data on several sheet depending on their type (Character, Kart…)

library(readxl)
Char<- read_excel("Mario Kart 8 Deluxe Stat.xlsx",sheet = "Char",col_names = T, col_types = c("text",rep("numeric", 12)))

Kart <- read_excel("Mario Kart 8 Deluxe Stat.xlsx",sheet = "Kart",col_names = T, col_types = c("text",rep("numeric", 12)))

Tire <- read_excel("Mario Kart 8 Deluxe Stat.xlsx",sheet = "Tire",col_names = T, col_types = c("text",rep("numeric", 12)))

Glide <- read_excel("Mario Kart 8 Deluxe Stat.xlsx",sheet = "Glide",col_names = T, col_types = c("text",rep("numeric", 12)))

´Type´ column is added in each dataset before joining all of them :

Char<-Char %>% add_column(Type="Char", .after='ID')
Kart <- Kart %>% add_column(Type="Kart", .after='ID')
Tire <- Tire %>% add_column(Type="Tire", .after='ID')
Glide <- Glide %>% add_column(Type="Glide", .after='ID')

data1 <-  full_join(Char, Kart) %>% full_join(Glide) %>% full_join(Tire) #join datatables

Variables

Each character and item has its own stats that adds up on 5 jauges :

  • Speed : Maximum speed,
  • Acceleration : Time to get to maximum speed
  • Weight : Higher weight can push lower weights out of the way
  • Handling : General kart handling for turns and drifts
  • Traction : Higher traction means the kart is less affected when out of track
  • M-Turbo : Higher M-Turbo results in longer and faster boost but also takes less time to stack up

IMAGE STAT

Dimension

44 characters 36 Karts 21 Tires 14 Gliders

IMAGE mariokart builder

Meet the data

Variables Summmaries and Distribution

Let’s take a glimpse at our data. Since, characters have base caracteristics and parts are only modificators (bonus and malus), 2 tables are generated :

data1 %>% filter(Type=="Char") %>% select_if(is.numeric) %>% skim_to_wide() %>% kable("html") %>% kable_styling(bootstrap_options = "striped")
type variable missing complete n mean sd p0 p25 median p75 p100 hist
numeric Accel 0 16 16 3.69 0.44 3 3.25 3.75 4 4.25 ▃▆▁▃▃▁▇▆
numeric Handling_Anti-G 0 16 16 3.8 0.73 2.5 3.25 3.75 4.31 5 ▃▂▃▇▂▃▃▃
numeric Handling_Gliding 0 16 16 3.8 0.73 2.5 3.25 3.75 4.31 5 ▃▂▃▇▂▃▃▃
numeric Handling_Land 0 16 16 3.8 0.73 2.5 3.25 3.75 4.31 5 ▃▂▃▇▂▃▃▃
numeric Handling_Water 0 16 16 3.3 0.73 2 2.75 3.25 3.81 4.5 ▃▂▃▇▂▃▃▃
numeric M-turbo 0 16 16 3.41 0.4 2.75 3.19 3.5 3.75 4 ▃▃▁▆▇▁▆▃
numeric Speed_Anti-G 0 16 16 3.22 0.84 2 2.5 3.25 3.81 4.5 ▇▅▂▇▅▂▂▇
numeric Speed_Gliding 0 16 16 3.97 0.84 2.75 3.25 4 4.56 5.25 ▇▅▂▇▅▂▂▇
numeric Speed_Land 0 16 16 3.47 0.84 2.25 2.75 3.5 4.06 4.75 ▇▅▂▇▅▂▂▇
numeric Speed_Water 0 16 16 3.72 0.84 2.5 3 3.75 4.31 5 ▇▅▂▇▅▂▂▇
numeric Traction 0 16 16 3.59 0.41 3 3.25 3.62 3.81 4.25 ▃▇▁▃▇▁▃▃
numeric Weight 0 16 16 3.19 0.85 2 2.5 3.12 3.81 4.5 ▇▅▅▅▅▂▂▇

Note that :

  • Handling and Speed are different on the type of environment : soil, glide, anti-gravity and water.
  • Values are distributed between 2 and 5
  • None of the variables are following normal distribution
  • No value is missing
data1 %>% filter(Type!="Char") %>% select_if(is.numeric) %>% skim_to_wide() %>% kable("html") %>% kable_styling(bootstrap_options = "striped")
type variable missing complete n mean sd p0 p25 median p75 p100 hist
numeric Accel 0 27 27 -0.15 0.49 -1 -0.5 0 0.25 0.75 ▃▅▇▅▇▇▅▂
numeric Handling_Anti-G 0 27 27 -0.065 0.31 -0.75 -0.25 0 0.12 0.5 ▁▂▁▆▇▁▅▂
numeric Handling_Gliding 0 27 27 -0.16 0.33 -0.75 -0.5 0 0 0.25 ▂▅▁▃▁▇▁▅
numeric Handling_Land 0 27 27 -0.065 0.34 -0.75 -0.25 0 0.25 0.5 ▂▂▁▅▇▁▅▂
numeric Handling_Water 0 27 27 0.065 0.38 -0.75 -0.12 0 0.25 0.75 ▁▃▃▇▁▇▃▂
numeric M-turbo 0 27 27 -0.056 0.47 -1 -0.25 0 0.25 0.75 ▁▃▂▇▇▇▃▂
numeric Speed_Anti-G 0 27 27 0.0093 0.31 -0.75 -0.12 0 0.25 0.5 ▁▂▁▃▇▁▅▂
numeric Speed_Gliding 0 27 27 -0.19 0.28 -0.75 -0.38 -0.25 0 0.25 ▂▃▁▆▁▇▁▂
numeric Speed_Land 0 27 27 -0.019 0.35 -0.75 -0.25 0 0.25 0.5 ▁▃▁▇▇▁▇▅
numeric Speed_Water 0 27 27 -0.12 0.29 -0.75 -0.25 -0.25 0 0.5 ▁▂▁▇▇▁▂▂
numeric Traction 0 27 27 -0.046 0.52 -1.25 -0.25 0 0.25 1 ▂▂▂▅▇▆▃▂
numeric Weight 0 27 27 -0.0093 0.34 -0.5 -0.25 0 0.25 0.5 ▆▆▁▇▁▇▁▅

Note that :

  • Kart parts are also affected by environment.
  • Values are distributed between -1 and +0.75
  • No value is missing

For now, mean speed and mean handling are used, we go into details later.

data<- data1 %>%mutate(Speed_mean=rowMeans(data1[grepl("Speed", names(data1))])) %>% mutate(Handling_mean=rowMeans(data1[grepl("Handling", names(data1))]))

data %>% filter(Type!="Char")%>% select(contains("mean")) %>% skim_to_wide() %>% kable("html") %>% kable_styling(bootstrap_options = "striped")
type variable missing complete n mean sd p0 p25 median p75 p100 hist
numeric Handling_mean 0 27 27 -0.056 0.27 -0.62 -0.19 0 0.16 0.44 ▂▃▁▂▇▂▅▁
numeric Speed_mean 0 27 27 -0.079 0.15 -0.5 -0.16 -0.062 0 0.19 ▁▁▁▂▁▇▂▁

Variables Correlations

I generally like to check correlations between variables in a dataset. For this I use the GGally package.

It looks like Most variables are highly correlated :

  • Acceleration, Traction, Handling and Mini-Turbo are positively correlated together
  • Speed and Weight are correlated together but negativaly to others
data %>% filter(Type=="Char") %>% select_if(is.numeric) %>% select(-(matches('Land|Gliding|Water|Anti'))) %>% ggpairs()

A nice way to summarize all these correlations and observe their relations is to plot a parallel coordinates (or sankey chart). Also available in GGally package, that’s or luck !

data %>% 
  filter(Type=="Char") %>% 
  select_if(is.numeric) %>% 
  select(-(matches('Land|Gliding|Water|Anti'))) %>% 
  select(Weight, Speed_mean, `M-turbo`,Traction,Accel,Handling_mean)%>% #Correlated variables are put close to each others
  ggparcoord(scale='globalminmax', groupColumn = "Weight")+
  scale_color_viridis_c(direction = -1,begin = 0.3) +
  theme(panel.grid.minor.y = element_blank(), panel.grid.major.x=element_line(color="grey60", linetype = 3),axis.ticks = element_blank(), panel.background = element_blank(), axis.text.x = element_text(angle=15), axis.title = element_blank()) + scale_x_discrete(position="top") + labs(y="Value")

Game Design analysis

As longtime Nintendo fan, I know that the company puts effort into creating immersive experiences and comprehensive environment. Well, ok it’s always somehow weird and cartoony… but my point is that there’s always something players can rely on and feel as “normal” first (physics, storyline, characters feelings …) before acceptiong all the crazy things that are happening around him.

That door into reality for Mario Kart is characters weights and physics : the player expects Bowser to weight more than Toad and thus be less agile while the latter have less inertia (thus lower maximum speed). The maths behind Mario Kart’s game design can be summed with the following chart :

plot_ly(data = (data %>% filter(Type=="Char")), x=~Speed_mean,y=~Accel, label=~ID, color=~`M-turbo`,size=~Weight,title="test",  text=~paste("Char :",ID, "<br> Weight :", Weight,"<br> M-Turbo :",`M-turbo`)) %>% hide_colorbar() %>% layout(title="Characters distribution between Acceleration, Mean Speed, Turbo and Weight", subtitle="Light characters have higher Acceleration, Turbo (+ Handling & Traction since positively correlated) and \nlower Mean Speed while heavy characters behave in an opposite way")

It is also true with Kart parts (Here : Tires)

ggplot(data %>% filter(Type=="Tire"),
  aes(x=Speed_mean,y=Accel))+
  geom_point(aes(size=Weight, fill=`M-turbo`), 
             pch=21)+
  ggrepel::geom_text_repel(aes(label=ID),
                           min.segment.length = 0.1,
                           point.padding = unit(1.2, 'lines'),
                           box.padding = unit(0.6, 'lines'),
                           force=2)+
  viridis::scale_fill_viridis()+
  geom_rect(aes(ymin=-Inf, ymax=0, xmin=-Inf, xmax=0),
            alpha=0.01,
            fill="red")+
  geom_rect(aes(ymin=0, ymax=Inf, xmin=0, xmax=Inf),
            alpha=0.01,
            fill="green")+
  labs(
    title="Tires distribution between Acceleration, Mean Speed, Turbo and Weight",
    subtitle="Ligher Tires grant higher Acceleration, Turbo (+ Handling & Traction since positively correlated) bonus and \n Mean Speed malus while heavy characters behave in an opposite way",
    caption= "Some kart parts grants malus only, none grants bonus only")

Weight can be consideredas a player preference depending on the kind of gameplay (or character) they like :

  • High wheight have generaly the best speed. Fits players that can dodge obstacle, don’t want to be pushed and knows the tracks perfectly.
  • Low wheight have best acceleration. Fits players that want return to max speed faster and wants to trigger turbo more ofter/powerfully

Questions and Hypothesis

Is there any best combination for characters and kart parts ?

The parameters that need to be optimized (i.e. higher values are the best) are :

Weight is put aside because as we seen, it is not considered as a performance but as type of gameplay or preference.

My first hypothesis was that the best combination would come from :

Analysis

Combinations computing

First, I tried to identify best Characters/Kart parts separatly, but I realised that if I could compute all the combination, finding the best combo would be the best way to get a clean answer

comb <- as_data_frame(permutations(n=length(data1$ID),r=4,v=data1$ID, repeats.allowed = F)) %>% 
  filter(V1 %in% Char$ID & V2 %in% Kart$ID & V3 %in% Tire$ID & V4 %in% Glide$ID) #Generates all the combinations

#following lines merge extracts stats for each characters or parts in differents tables
names(comb)[1] <- "ID"
comb1 <- inner_join(comb, data, by="ID")

names(comb)[1] <- "V1"
names(comb)[2] <- "ID"
comb2 <- inner_join(comb, data, by="ID")

names(comb)[2] <- "V2"
names(comb)[3] <- "ID"
comb3 <- inner_join(comb, data, by="ID")

names(comb)[3] <- "V3"
names(comb)[4] <- "ID"
comb4 <- inner_join(comb, data, by="ID")

data_combo <- comb1[,6:19]+comb2[,6:19]+comb3[,6:19]+comb4[,6:19]

data_combo <- cbind(comb[,1:4], data_combo)

#Computation of combinations stats
data_combo <- comb1[,6:19]+comb2[,6:19]+comb3[,6:19]+comb4[,6:19]

data_combo <- cbind(comb[,1:4], data_combo)

names(data_combo)[1]<-"Char"
names(data_combo)[2]<-"Kart"
names(data_combo)[3]<-"Tire"
names(data_combo)[4]<-"Glidder"

data_combo$ID <- seq_len(nrow(data_combo))

#Mean Handling and Accelarations are recomputed and a Acceleration / Speed variable is added
data_combo  <- data_combo  %>%mutate(Speed_mean=rowMeans(data_combo [grepl("Speed", names(data_combo ))])) %>% mutate(Handling_mean=rowMeans(data_combo [grepl("Handling", names(data_combo ))]))

More variables !

At this point, I decided to add few variables that made sense :

  • Weighted Speed and Handling that gives more importance (twice) to Land and Anti-G (These kind of tracks seems to be the more frequent. I could not find any actual data on that but a 2 factor seems appropriate)
  • As weight is not a performance, weights are transformed in categories
#Land Speed and Handling are weighted (twice more important) for mean calculation since most part of the races happens on regular soil.
data_combo  <- data_combo %>%
  rowwise() %>%
  mutate(Speed_mean=weighted.mean(x=c(Speed_Land,`Speed_Anti-G`, Speed_Water, Speed_Gliding), w=c(2,2,1,1))) %>%
  rowwise() %>%
  mutate(Handling_mean=weighted.mean(x=c(Handling_Land,`Handling_Anti-G`, Handling_Water, Handling_Gliding), w=c(2,2,1,1)))


data_combo <- data_combo %>% mutate(total_perf=as.numeric(Accel+Speed_mean+Handling_mean+Traction+`M-turbo`)) 

#Weight categories
data_combo <- data_combo %>% mutate(w_cat = cut(Weight,breaks = c(0,2,3,4,5,6),labels = c("Super Light","Light","Medium","Heavy", "Super Heavy")))#Create Weigh categories


#A Accelration/Speed Ratio is computed
data_combo <- data_combo %>% mutate(A_SRatio= `Accel`/Speed_mean)

Combinations summary

library(skimr)
data_combo %>% ungroup() %>% 
  select(-ID) %>% 
  select_if(is.numeric) %>% 
  skim_to_wide() %>% 
  kable("html") %>% 
  kable_styling()
type variable missing complete n mean sd p0 p25 median p75 p100 hist
numeric A_SRatio 0 8064 8064 1.13 0.55 0.2 0.72 1.02 1.42 4.18 ▅▇▅▂▁▁▁▁
numeric Accel 0 8064 8064 3.4 0.83 1 2.75 3.5 4 5.75 ▁▂▇▇▇▇▂▁
numeric Handling_Anti-G 0 8064 8064 3.53 0.84 1 3 3.5 4.25 5.75 ▁▁▆▆▆▇▂▁
numeric Handling_Gliding 0 8064 8064 3.5 0.85 1 3 3.5 4 5.75 ▁▂▆▆▇▇▂▁
numeric Handling_Land 0 8064 8064 3.61 0.85 1 3 3.75 4.25 5.75 ▁▁▅▆▆▇▃▁
numeric Handling_mean 0 8064 8064 3.54 0.81 1.12 2.96 3.54 4.12 5.67 ▁▂▅▇▇▆▃▁
numeric Handling_Water 0 8064 8064 3.43 0.85 1 2.75 3.5 4 5.75 ▁▂▇▇▇▇▂▁
numeric M-turbo 0 8064 8064 3.35 0.8 1 2.75 3.25 4 5.75 ▁▂▇▇▇▇▂▁
numeric Speed_Anti-G 0 8064 8064 3.37 0.93 0.75 2.75 3.25 4 5.75 ▁▂▅▇▆▆▂▁
numeric Speed_Gliding 0 8064 8064 3.43 0.92 1 2.75 3.5 4.25 5.75 ▁▂▇▆▆▇▃▁
numeric Speed_Land 0 8064 8064 3.36 0.96 0.75 2.75 3.25 4 5.75 ▁▃▅▇▅▆▂▁
numeric Speed_mean 0 8064 8064 3.37 0.86 1.38 2.67 3.38 4.04 5.21 ▁▅▇▇▇▇▆▂
numeric Speed_Water 0 8064 8064 3.33 0.92 1 2.5 3.25 4 5.75 ▁▃▇▆▆▇▂▁
numeric total_perf 0 8064 8064 16.97 2.1 10.38 15.54 17.04 18.5 22.58 ▁▁▃▆▇▆▃▁
numeric Traction 0 8064 8064 3.31 0.84 0.75 2.75 3.25 4 5.75 ▁▂▃▇▆▆▁▁
numeric Weight 0 8064 8064 3.18 0.98 0.75 2.5 3.25 4 5.75 ▁▃▅▇▅▆▂▁

Best overall combination ?

ggplot(data_combo, aes(Speed_mean, Accel, label=paste(round(total_perf,2),round(Speed_mean,2), round(Accel,2),"\n",Char,"+",Kart,"+", Tire,"+", Glidder)))+
  geom_point(pch=21, alpha=0.95, aes(fill=`M-turbo`, size=Handling_mean))+
  viridis::scale_fill_viridis(direction=1)+
  geom_abline(intercept=0, slope=1,size=1.5)+
  geom_vline(xintercept=4)+
  geom_hline(yintercept=4)+
  ggrepel::geom_label_repel(data=(data_combo %>% filter(Speed_mean >= 4 & Accel >= 4)%>% top_n(3,total_perf)), aes(x=Speed_mean, y=Accel,label=paste(round(total_perf,2),round(Speed_mean,2), round(Accel,2),"\n",Char,"+",Kart,"+", Tire,"+", Glidder)), size=2.6, segment.colour = "black",force=9,point.padding = unit(0.5, "lines"), box.padding = 0, color="black", fill="white", alpha=0.85, segment.size = 1, nudge_y = 0.5)+
  facet_wrap(~w_cat)

Comparative Chart of top_n

Best_Over_Combo <-  data_combo %>% filter(Speed_mean >= 4 & Accel >= 4)%>% top_n(5,total_perf) %>% select(ID, Char, Kart, Tire, Glidder)

ggplot(gather((data_combo %>% filter(ID %in% Best_Over_Combo$ID)),key = Stat, value=Stat_v, c(Speed_mean,Speed_Land:Speed_Gliding)),aes(paste(Char, Kart,Tire,Glidder), Stat_v))+geom_col(aes(fill=paste(Char, Kart,Tire,Glidder)))+facet_grid(Stat~., scales = "free")+theme(axis.text.y = element_blank(), legend.position = "bottom", legend.text = element_text(size=7), axis.ticks.y = element_blank(), legend.direction = "vertical", legend.title.align = 0.5)+guides(fill=guide_legend(ncol=3))+labs(fill="Combo", x="", y="Scores")+scale_fill_brewer(type = "qual")

Problem here is that Speed_Land is low. Speen_mean was compensated by the High Speed_Water

ggplot(gather(data_combo %>% filter(Accel>=4 & Speed_mean>5& Handling_mean>4) %>% top_n(1, total_perf),key = Stat, value=Stat_v, c(Speed_Land:Speed_Gliding,Accel, Weight, Handling_Land:Handling_Gliding, Traction, `M-turbo`)),aes(Stat, Stat_v))+geom_col()+coord_flip()

Best combinations for Yoshi

data_combo %>%filter(Char=="Peach", Speed_Land >3) %>% select(Char, Kart, Tire, Glidder,total_perf,Weight, Accel, Speed_mean,Speed_Land, Handling_mean, `M-turbo`,Traction, Handling_Land) %>% arrange(desc(total_perf)) %>% top_n(10, Accel)
## # A tibble: 20 x 13
##    Char  Kart  Tire  Glidder total_perf Weight Accel Speed_mean Speed_Land
##    <chr> <chr> <chr> <chr>        <dbl>  <dbl> <dbl>      <dbl>      <dbl>
##  1 Peach W 25… Stan… Super …       19.4   2.75    4.       3.50       3.25
##  2 Peach W 25… Stan… Wario …       19.1   3.00    4.       3.54       3.25
##  3 Peach W 25… Off-… Cloud …       19.0   2.75    4.       3.50       3.25
##  4 Peach Land… Off-… Super …       18.8   2.75    4.       3.21       3.25
##  5 Peach Blue… Roll… Super …       18.8   2.00    4.       3.33       3.25
##  6 Peach Cat … Stan… Super …       18.8   3.00    4.       3.46       3.25
##  7 Peach Stan… Stan… Cloud …       18.7   2.75    4.       3.50       3.25
##  8 Peach W 25… Off-… Peach …       18.7   3.00    4.       3.46       3.25
##  9 Peach Land… Off-… Wario …       18.6   3.00    4.       3.25       3.25
## 10 Peach Blue… Roll… Wario …       18.5   2.25    4.       3.38       3.25
## 11 Peach Blue… Butt… Cloud …       18.5   1.75    4.       3.46       3.25
## 12 Peach Cat … Stan… Wario …       18.5   3.25    4.       3.50       3.25
## 13 Peach Pipe… Off-… Super …       18.5   3.00    4.       3.17       3.25
## 14 Peach Cat … Off-… Cloud …       18.4   3.00    4.       3.46       3.25
## 15 Peach Stan… Stan… Peach …       18.4   3.00    4.       3.46       3.25
## 16 Peach Stan… Butt… Super …       18.3   2.50    4.       3.38       3.25
## 17 Peach Pipe… Off-… Wario …       18.2   3.25    4.       3.21       3.25
## 18 Peach Blue… Butt… Peach …       18.2   2.00    4.       3.42       3.25
## 19 Peach Stan… Butt… Wario …       18.1   2.75    4.       3.42       3.25
## 20 Peach Cat … Off-… Peach …       18.0   3.25    4.       3.42       3.25
## # ... with 4 more variables: Handling_mean <dbl>, `M-turbo` <dbl>,
## #   Traction <dbl>, Handling_Land <dbl>
ggplot(data_combo %>%filter(Char=="Peach"), aes(Speed_mean, Accel))+
  geom_point(pch=21, alpha=0.85, aes(fill=`M-turbo`, size=Handling_mean))+
  ggrepel::geom_label_repel(data=(data_combo %>%filter(Char=="Peach", Speed_mean >= 3 & Accel >= 3) %>% top_n(2, total_perf)), aes(x=Speed_mean, y=Accel,label=paste(total_perf,"\n",Kart,"+", Tire,"+", Glidder,"\n", w_cat)), size=2.6, segment.colour = "black",force=5,point.padding = unit(0.5, "lines"), box.padding = 1.5, color="black", fill="white", alpha=0.87, direction = "y")+
  scale_fill_distiller(direction=1, palette="Greens")

cols_fill <- c("Baby Mario" = "#F50000",
               "Baby Peach" = "#F4B1D5",
               "Baby Rosalina" = "#FFFF8E",
               "Bowser" = "#F9B40E",
               "Cat peach" = "#FCF7F6",
               "Koopa Troopa" = "#E8B500",
               "Luigi" = "#289913",
               "Mario" = "#D30000",
               "Metal Mario" = "#C5C5C5",
               "Peach" = "#D39884",
               "Rosalina" = "#FFFBDF",
               "Tanooki Mario" = "#8A4300",
               "Toad" = "#FDF4F3",
               "Toadette" = "#F67CC2",
               "Waluigi" = "#430C90",
               "Wario" = "#FFF300"
               )

cols_col <- c("Baby Mario" = "#FFE4BF",
               "Baby Peach" = "#FEF459",
               "Baby Rosalina" = "#ECC69A",
               "Bowser" = "#FE0A00",
               "Cat peach" = "#FCF7F6",
               "Koopa Troopa" = "#E8B500",
               "Luigi" = "#289913",
               "Mario" = "#D30000",
               "Metal Mario" = "#C5C5C5",
               "Peach" = "#D39884",
               "Rosalina" = "#FFFBDF",
               "Tanooki Mario" = "#8A4300",
               "Toad" = "#FE0A00",
               "Toadette" = "#F67CC2",
               "Waluigi" = "#430C90",
               "Wario" = "#FFF300"
               )

ggplot(data_combo %>% filter(Char=="Toad"), aes(Speed_mean, Accel))+
  geom_point(pch=21, size=2, aes(alpha=`M-turbo`, size=Handling_mean, fill=Char))+
  geom_abline(slope=1)

Plot with images

Plot with transparent image (limited loop)

MarioK_img <- image_read("Mario Kart.png")
image_info(MarioK_img)
##   format width height colorspace matte filesize density
## 1    PNG  1678    957       sRGB  TRUE  1601520   37x37
for (j in c(rep(752,1), rep(820,1), rep(882,1))) {
  for(i in c((0:24),(0:24),(0:11))) {
  frame <- paste("60x60+",i*65+2,"+",j, sep="")
  name <- paste("img",i,j,sep="_")
  assign(x = name,value = image_crop(MarioK_img, frame)) %>%image_write(.,path = paste("icons/img",i,j,".png", sep="_"), format="png")
  
    m <- readPNG(paste("icons/img",i,j,".png",sep="_"), F)
    w <- m
    w[,,4] <- m[,,4]*0.6 # adjust alpha
    writePNG(w, paste(paste("icons/img",i,j,".png", sep="_")))
  }
}
## Error in file(con, "wb"): impossible d'ouvrir la connexion